<?php

namespace Route;

use Firebase\JWT\JWT;
use Swoole\Table;
use Swoole\WebSocket\Server;

/**
 * Created by PhpStorm.
 * User: Administrator
 * Date: 2019/3/27 0027
 * Time: 20:51
 */
class ServiceCenter
{
    private $server;
    protected $redis;
    protected $table;

    public function run()
    {
        $wsConfig = Config::get('ws');

        $this->server = new Server($wsConfig['host'], $wsConfig['port']);
        $this->server->set($wsConfig['ws_setting']);

        $this->server->on('open', [$this, 'open']);
//        $this->server->on('handshake', [$this, 'handshake']);
        $this->server->on('message', [$this, 'message']);
        $this->server->on('workerStart', [$this, 'workerStart']);
        $this->server->on('close', [$this, 'close']);
        $this->server->on('request', [$this, 'request']);
        $this->createTable();
        $this->server->start();
    }

    public function createTable()
    {
        $this->table = new Table(1024);
        $this->table->column('ack', Table::TYPE_INT, 1);
//        $this->table->column('pad',Table::TYPE_STRING, 1);
        $this->table->create();
    }

    public function workerStart($server, $request)
    {
        $this->redis = new \Redis();
        //$this->redis->setOption();//超时时间 设置socket超时间
        $this->redis->pconnect('127.0.0.1', 6379);
        $this->redis->auth('redis123456');
    }

//    public function handshake(\swoole_http_request $request, \swoole_http_response $response)
//    {
//        //通过判断websocket请求头当中是否有一个有效token
//        if (!isset($request->header['sec-websocket-protocol'])) {
//            //$response->end();
//            //return false;
//            //jwt验证
//        }
//        // websocket握手连接算法验证
//        $secWebSocketKey = $request->header['sec-websocket-key'];
//        $patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
//        if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
//            $response->end();
//            return false;
//        }
//        echo $request->header['sec-websocket-key'];
//        $key = base64_encode(sha1(
//            $request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
//            true
//        ));
//
//        $headers = [
//            'Upgrade' => 'websocket',
//            'Connection' => 'Upgrade',
//            'Sec-WebSocket-Accept' => $key,
//            'Sec-WebSocket-Version' => '13',
//        ];
//
//        // WebSocket connection to 'ws://127.0.0.1:9502/'
//        // failed: Error during WebSocket handshake:
//        // Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
//        if (isset($request->header['sec-websocket-protocol'])) {
//            $headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
//        }
//
//        foreach ($headers as $key => $val) {
//            $response->header($key, $val);
//        }
//
//        $response->status(101);
//        $response->end();
//    }

    public function open($server, $request)
    {

    }

    /**
     * @description:使用jwt制作token
     * @auth:yfl
     * @email:554665488@qq.com
     * @param $id
     * @param $url
     * @return string
     * @throws \Exception
     */
    private function issue($id, $url)
    {
        $key = Config::get('JwT_key');
        $time = time();
        $token = [
            'iss' => '',
            'aud' => '',
            'iat' => $time,//颁发时间
            'nbf' => $time,//生效时间
            'exp' => $time + 7200,//过期时间
            'data' => [
                'uid' => $id,//客户端发送过来的uid
                'name' => 'yfl' . $id,
                'service_url' => $url
            ]
        ];
        return JWT::encode($token, $key);
    }

    /**
     * @description:客户端http请求获取token和服务地址
     * @auth:yfl
     * @email:554665488@qq.com
     * @param $request
     * @param $response
     * @throws \Exception
     */
    public function request($request, $response)
    {
        $response->header('Access-Control-Allow-Origin', "*");
        $response->header('Access-Control-Allow-Methods', "GET,POST,OPTIONS");
        $post = $request->post;
        switch ($post['method']) {
            case 'login':
                //1.权限验证,颁发token
                //2.通过算法返回服务器地址跟token
                $ipPortInfo = $this->getServiceIpPort();
                if ($ipPortInfo == false) throw new \Exception('获取服务失败');
                $ipPortInfo = json_decode($ipPortInfo, true);
                $url = $ipPortInfo['ip'] . ':' . $ipPortInfo['port'];
                $token = $this->issue($post['id'], $url);
                $response->end(json_encode(['token' => $token, 'url' => $url]));
                break;
        }
    }

    /**
     * @description:注册服务检测服务的存活状态
     * @auth:yfl
     * @email:554665488@qq.com
     * @param $server
     * @param $frame
     * @throws \Exception
     */
    public function message($server, $frame)
    {
        $data = json_decode($frame->data, true);
        $fd = $frame->fd;
        switch ($data['method']) {
            case 'register':
//                $service_key = 'service:' . $service['service'];
                $service_key = 'im_service';
                //$service_key=>118.24.109.254:9800
                //              118.24.109.254:9801
                $serviceIpPort = json_encode([
                    'ip' => $data['ip'],
                    'port' => $data['port']
                ]);
                $this->redis->sadd($service_key, $serviceIpPort);
                $redis = $this->redis;
                //会存在服务宕机的情况 主动消除服务
                $server->tick(3000, function ($timer_id, $redis, $server, $service_key, $fd, $serviceIpPort) {
                    //检测fd是否存活
                    if (!$server->exist($fd)) {
                        var_dump($serviceIpPort . 'im宕机了，主动清除');
                        //集合是否存在key
                        if ($redis->sMembers($service_key)) {
                            //清除服务
                            $redis->srem($service_key, $serviceIpPort);
                            //清除定时器
                        }
                        $server->clearTimer($timer_id);
                    }
                }, $redis, $server, $service_key, $fd, $serviceIpPort);
                break;
            case 'route_broadcast':
                $server_urls = $data['target_server'];
                foreach ($server_urls as $server_url) {
                    //给IM-SERVER发送得我广播消息
                    $push_data = json_decode($server_url, true);
                    $this->sendOtherImServer($push_data['ip'], $push_data['port'], $data['msg']);
                }
                break;
        }

    }

    public function close($server, $fd)
    {

    }

    /**
     * @description:广播转发给其他服务的用户
     * @auth:yfl
     * @email:554665488@qq.com
     * @param $ip
     * @param $port
     * @param $msg
     * @throws \Exception
     */
    public function sendOtherImServer($ip, $port, $msg)
    {
        //请求其他im_server 需要token验证
        $token = $this->issue(0, '106.14.120.16:9600');
        $cli = new \Swoole\Coroutine\http\Client($ip, $port);
        //携带token
        $cli->setHeaders(['sec-websocket-protocol' => $token]);
        $ret = $cli->upgrade("/"); //升级的websockt
        if ($ret) {
            $data = [
                'method' => 'route_broadcast', //方法
                'msg' => $msg,
            ];
            $cli->push(json_encode($data));
        }
    }

    /**
     * @description:从redis中轮训获取服务地址
     * @auth:yfl
     * @email:554665488@qq.com
     * @param string $service_key
     * @return bool|mixed
     */
    private function getServiceIpPort($service_key = 'im_service')
    {
        if ($this->redis->sMembers($service_key)) {
            $serviceInfo = $this->redis->smembers('im_service');
//            $serviceInfo = [
//                ['ip' => '1.1.1.1', 'port' => '9501'],
//                ['ip' => '2.2.2.2', 'port' => '9502'],
//                ['ip' => '3.3.3.3', 'port' => '9503']
//            ];
//            array_push($serviceInfo,'{"ip":"106.14.120.16","port":9503,"2"=>2}');
            if (!empty($serviceInfo)) return Round::select($serviceInfo);
        }

        return false;
    }
}
